'use strict';
// let Buffer = require("buffer");
// let EC = require("elliptic").ec;

import {Buffer}  from "buffer"
import { ec as EC } from "elliptic";
import crypto from 'crypto';

let ec = new EC("secp256k1");
const VERSION = 0x01;
const EC_GROUP_ORDER = Buffer.from('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141', 'hex');
const ZERO32 = Buffer.alloc(32, 0);

function aesCbcEncrypt(iv, key, data) {
    let cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
    let firstChunk = cipher.update(data);
    let secondChunk = cipher.final();
    return Buffer.concat([firstChunk, secondChunk]);
}
function aesCbcDecrypt(iv, key, data) {
    let decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
    let firstChunk = decipher.update(data);
    let secondChunk = decipher.final();
    return Buffer.concat([firstChunk, secondChunk]);
}

function assert(condition, message) {
    if (!condition) {
        throw new Error(message || "Assertion failed");
    }
}

function pad32(msg){
    let buf;
    if (msg.length < 32) {
        buf = Buffer.alloc(32);
        buf.fill(0);
        msg.copy(buf, 32 - msg.length);
        return buf;
    } else {
        return msg;
    }
}

function isScalar (x) {
    return Buffer.isBuffer(x) && x.length === 32;
}

function isValidPrivateKey(privateKey) {
    if (!isScalar(privateKey))
    {
        return false;
    }
    return privateKey.compare(ZERO32) > 0 && // > 0
        privateKey.compare(EC_GROUP_ORDER) < 0; // < G
}

/* This must check if we're in the browser or
not, since the functions are different and does
not convert using browserify */
function randomBytes(size) {
    return Buffer.from(crypto.randomBytes(size));
}

function getPublic(privateKey) {
    // This function has sync API so we throw an error immediately.
    assert(privateKey.length === 32, "Bad private key");
    assert(isValidPrivateKey(privateKey), "Bad private key");
    return Buffer.from(ec.keyFromPrivate(privateKey).getPublic(true, "arr"));
}
function getPublicNew(privateKey) {
    // This function has sync API so we throw an error immediately.
    assert(privateKey.length === 32, "Bad private key");
    assert(isValidPrivateKey(privateKey), "Bad private key");
    return Buffer.from(ec.keyFromPrivate(privateKey).getPublic(false, "arr"));
}

/**
 * Generate a new valid private key. Will use the window.crypto or window.msCrypto as source
 * depending on your browser.
 * @return {Buffer} A 32-byte private key.
 * @function
 */
function generatePrivate() {
    let privateKey = randomBytes(32);
    while (!isValidPrivateKey(privateKey)) {
        privateKey = randomBytes(32);
    }
    return privateKey;
}

function derive(privateKeyA, publicKeyB) {
    assert(Buffer.isBuffer(privateKeyA), "Bad private key");
    assert(Buffer.isBuffer(publicKeyB), "Bad public key");
    assert(privateKeyA.length === 32, "Bad private key");
    assert(isValidPrivateKey(privateKeyA), "Bad private key");
    assert(publicKeyB.length === 65 || publicKeyB.length === 33, "Bad public key");
    if (publicKeyB.length === 65)
    {
        assert(publicKeyB[0] === 4, "Bad public key");
    }
    if (publicKeyB.length === 33)
    {
        assert(publicKeyB[0] === 2 || publicKeyB[0] === 3, "Bad public key");
    }
    let keyA = ec.keyFromPrivate(privateKeyA);
    let keyB = ec.keyFromPublic(publicKeyB);
    let Px = keyA.derive(keyB.getPublic());  // BN instance
    return Buffer.from(Px.toArray());
}

function sign(privateKey, msg) {
    assert(privateKey.length === 32, "Bad private key");
    assert(isValidPrivateKey(privateKey), "Bad private key");
    assert(msg.length > 0, "Message should not be empty");
    assert(msg.length <= 32, "Message is too long");
    return Buffer.from(ec.sign(msg, privateKey, {canonical: true}).toDER());
}

function verify(publicKey, msg, sig) {
    assert(msg.length > 0, "Message should not be empty");
    assert(msg.length <= 32, "Message is too long");
    msg = pad32(msg);
    return ec.verify(msg, sig, publicKey);
}

function encrypt(publicKeyTo, msg) {
    // generate session key pair
    let sessionPublicKey, sessionPrivateKey;
    sessionPrivateKey = generatePrivate();
    sessionPublicKey = getPublic(sessionPrivateKey);
    // get shared private from ECDH
    let sharedPrivateKey = derive(sessionPrivateKey, publicKeyTo);
    // sharedPrivateKey.length == 32
    let iv = sharedPrivateKey.slice(0, 16);// use first 16 bytes as AES vi
    let encryptionKey = sharedPrivateKey.slice(0, 32);// use first 32 bytes as AES encrypt key
    let ciphertext = aesCbcEncrypt(iv, encryptionKey, msg);
    // encrypted encode format: version[1 byte], session public key[33 bytes], ciphertext
    return Buffer.concat([Buffer.from([VERSION]), sessionPublicKey, ciphertext])
}

function decrypt(privateKey, encrypted) {
    // let version = encrypted.slice(0, 1);
    let sessionPublicKey = encrypted.slice(1, 34);
    let ciphertext = encrypted.slice(34);
    let sharedPrivateKey = derive(privateKey, sessionPublicKey);
    let iv = sharedPrivateKey.slice(0, 16);
    let encryptionKey = sharedPrivateKey.slice(0, 32);
    let msg = aesCbcDecrypt(iv, encryptionKey, ciphertext);
    return Buffer.from(new Uint8Array(msg));
}

// 生成随机私钥
// module.exports.generatePrivate= generatePrivate;
// // 私钥转公钥
// module.exports.getPublic = getPublic;
// //私钥转公钥压缩版
// module.exports.getPublicNew = getPublicNew;
// // 签名
// module.exports.sign = sign;
// // 验签
// module.exports.verify = verify;
// // Diffie–Hellman密钥交换
// module.exports.derive = derive;
// // 公钥加密
// module.exports.encrypt = encrypt;
// // 私钥解密
// module.exports.decrypt = decrypt;

export default {generatePrivate,getPublic,getPublicNew,sign,verify,derive,encrypt,decrypt}
